Skip to content

perf(mpsc): replace the unbounded queue backend - #232

Open
tisonkun wants to merge 2 commits into
mainfrom
codex/rewrite-unbounded-mpsc
Open

perf(mpsc): replace the unbounded queue backend#232
tisonkun wants to merge 2 commits into
mainfrom
codex/rewrite-unbounded-mpsc

Conversation

@tisonkun

Copy link
Copy Markdown
Member

Summary

  • replace the standard-library unbounded channel backend with an internal segmented MPSC queue, without adding a runtime dependency
  • keep the public endpoint, ordering, close, error, and auto-trait contracts unchanged
  • extend the ecosystem benchmark with Crossbeam and representative create, one-shot, sequential, threaded MPSC, and async-contention workloads drawn from the Crossbeam, Flume, and Tokio suites
  • cover block traversal, per-producer FIFO ordering, and send/close reclamation races in focused queue tests

Part of #209.

Design Notes

The queue stores 31 messages per block. Producers reserve globally ordered slots through one tail CAS, while the unique consumer owns its position without a competing head CAS. The producer reserving the final slot installs the next block before publishing that slot; after reading the final slot, the consumer knows every producer touching the old block has finished and can reclaim it immediately. This follows the segmented-list layout used by crossbeam-channel, specialized for a single consumer and stripped of blocking, selection, and multi-consumer state.

Receiver close sets a bit in the same atomic tail position used for reservation. A sender either reserves before that operation and completes normally, or observes the bit and gets its value back. Cleanup waits only for already-reserved slots, drains them in FIFO order, and then reclaims the final block. Slot publication and the parked-receiver gate participate in one sequentially consistent order: after register -> arm -> recheck, the receiver either observes the completed slot or its producer observes the arm and performs the wake. Active-receiver sends only read the false gate, avoiding a shared AtomicWaker write on every message.

Unsafe code is confined to the queue module. Its invariants are that each successful tail reservation exclusively owns one slot, only the unique consumer reads slots, acquiring ready transfers the value, and FIFO traversal delays block reclamation until all producers using that block have published. The public receiver remains Send + Sync + Unpin; Sync is justified at the owned consumer core because shared references cannot advance or close it.

The benchmark shapes come from Tokio's MPSC benchmark, Crossbeam's channel benchmark, and Flume's basic benchmark. crossbeam-channel is benchmark-only. On a local Apple Silicon machine, using cargo bench -p benchmarks --bench ecosystem -- 'mpsc::unbounded' --min-time 1 --max-time 2, median results changed as follows:

Case main This PR Change
ready round trip 8.70 ns 5.35 ns -38.5%
try round trip 8.38 ns 4.58 ns -45.4%
sequential batch 114.9 Mitem/s 159.1 Mitem/s +38.5%
async contention, 5 tasks 9.44 Mitem/s 67.22 Mitem/s 7.1x
concurrent, 1 producer 92.95 Mitem/s 76.96 Mitem/s -17.2%
concurrent, 2 producers 16.29 Mitem/s 76.99 Mitem/s 4.7x
concurrent, 4 producers 7.24 Mitem/s 70.76 Mitem/s 9.8x
concurrent, 8 producers 4.51 Mitem/s 32.08 Mitem/s 7.1x
concurrent, 16 producers 3.54 Mitem/s 29.30 Mitem/s 8.3x
concurrent, 32 producers 3.55 Mitem/s 30.33 Mitem/s 8.5x

The same final run compared the implementations directly in Mitem/s:

Case Asyncband Crossbeam Flume Tokio async-channel
sequential batch 159.1 113.7 87.20 110.8 36.86
async contention, 5 tasks 67.22 n/a 25.35 7.16 9.86
concurrent, 1 producer 76.96 111.2 7.41 35.37 30.85
concurrent, 2 producers 76.99 101.2 26.91 14.09 17.75
concurrent, 4 producers 70.76 100.7 23.86 10.34 11.58
concurrent, 8 producers 32.08 57.71 28.90 3.45 8.72
concurrent, 16 producers 29.30 23.66 30.70 1.93 8.84
concurrent, 32 producers 30.33 29.31 30.58 2.06 9.01

Validated with cargo x check, cargo x test, cargo x lint, and Miri over all focused queue tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant